📝 我的笔记

还没有笔记

选中页面文字后点击「高亮」按钮添加

文件 I/O

📜 原文
📖 逐步解释
∑ 公式拆解
💡 数值示例
⚠️ 易错点
📝 总结
🎯 存在目的
🧠 直觉心智模型
💭 直观想象

1文件 I/O

COMS W3157

Borowski 博士

1 输入 / 输出

2 getopt (现在在 C 中!)

```

int getopt(int argc, char *argv[],

char *optstring);

```

3 getopt (现在在 C 中!)

4 getopt (现在在 C 中!)

5 getopt 示例

```

int main(int argc, char **argv) {

bool aflag = false,

bflag = false;

char *cvalue = NULL;

int c;

// Suppress internal error messages.

opterr = 0;

while ((c = getopt(argc, argv, "abc:")) != -1) {

switch (c) {

case 'a':

aflag = true;

break;

case 'b':

bflag = true;

break;

case 'c':

cvalue = optarg;

break;

case '?':

fprintf(stderr,

"Error: Unknown option '-%c' received.\n",

optopt);

return EXIT_FAILURE;

default:

return EXIT_FAILURE;

}

}

```

```

printf("aflag = %d, bflag = %d, cvalue = %s\n",

aflag, bflag, cvalue);

printf("optind is now %d.\n", optind);

for (int index = optind; index < argc; index++) {

printf("Non-option argument: %s\n",

argv[index]);

}

return EXIT_SUCCESS;

```

6 getchar

最简单的机制是每次从标准输入读取一个字符。

7 putchar

int putchar(int) 用于输出。

8 getchar/putchar 示例

```

#include

#include

/ lower: convert input to lower case/

int main() {

int c;

while ((c = getchar()) != EOF) {

putchar(tolower(c));

}

return 0;

}

From Converts upper case chars to lower case and leaves other chars untouched.

```

9 scanf

```

int scanf(char *format, ...)

```

scanf 从标准输入读取字符,根据 format 中的规范解释它们,并通过其余参数存储结果。

scanf 在其 format 字符串中忽略空格和制表符。此外,它在查找输入值时会跳过空白字符(空格、制表符、换行符等)。

要读取格式不固定的输入,通常最好一次读取一行,然后用 scanf 拆分它。

最常见的格式说明符是用于 int 类型的 "\%d"、float 类型的 "\%f" 和 字符字符串 类型的 "\%s"。

示例:

```

int day, month, year;

scanf("%d/%d/%d", &month, &day, &year);

```

它返回成功匹配并分配的输入项的数量作为其值。

10 sscanf

还有一个 sscanf 函数,它从字符串而不是标准输入读取:

int sscanf(char string, char format, arg1, arg2, ...)

它根据 format 中的格式扫描字符串,并通过 arg1、arg2 等存储结果值。这些参数必须是指针。

format 字符串通常包含 转换规范,用于控制输入的转换。format 字符串可能包含:

它返回成功匹配并分配的输入项的数量作为其值。

11 行输入

```

char fgets(char line, int maxline,

FILE *fp)

```

fgets 从文件 fp 读取下一行输入(包括换行符)到字符数组 line 中;最多读取 maxline-1 个字符。

生成的行以 '\0' 终止。

fgets 返回 line;在文件结束或错误时返回 NULL。

12 文件访问

FILE *fp 称为 文件指针,它指向一个包含文件信息的结构体。此信息包括 缓冲区 的位置、缓冲区 中的当前字符位置、文件是正在读取还是写入、以及是否发生错误或文件结束。

13 打开文件

FILE fopen (char name, char *mode)

file open 接受包含文件名的 字符字符串 和打开文件的模式。mode 可以是

在某些系统上,如果您希望处理 二进制文件(即数据不仅仅是文本),您可以在模式字符串中添加字母 "b",例如 "rb"、"wb" 和 "ab"。

14 关闭文件

```

int fclose(FILE *fp)

```

是 fopen 的反向操作,因为它断开了文件指针与 fopen 建立的外部名称之间的连接,释放文件指针以用于另一个文件。

15 从文件读取/写入文件

从文件读取/写入文件对于允许我们的程序处理不同类型的输入和输出至关重要。

有许多函数可以实现这一点,但最重要的是以下几个:

16 单字符文件读写

int getc(FILE *fp)

返回由 fp 引用的流中的下一个字符;在文件结束或错误时返回 EOF。

int putc(int c, FILE *fp)

将字符 c 写入文件 fp,并返回写入的字符,如果发生错误则返回 EOF。

17 getc() 和 putc() 示例

```

#include

#include

/ lower: convert input to lower case/

int main() {

int c;

while ((c = getc(stdin)) != EOF) {

putc(tolower(c), stdout);

}

return 0;

}

```

18 多字节文件读写

```

size_t fread(void p, size_t size, size_t n, FILE file)

```

```

size_t fwrite(const void *p, size_t size, size_t n, FILE

```

*file)

19 fread() 和 fwrite() 示例

```

int array[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

FILE *fp = fopen("binary.dat", "wb");

for (int i = 0, end = sizeof(array)/sizeof(int); i < end; i++) {

fwrite(array + i, sizeof(int), 1, fp);

}

fclose(fp);

int num = 0;

fp = fopen("binary.dat", "rb");

while(fread(&num, sizeof(int), 1, fp) == 1) {

printf("%d\n", num);

}

fclose(fp);

```

20 文件定位函数

随后的读取或写入将从新位置开始访问数据。对于 二进制文件,位置设置为从 origin 偏移的字符数,origin 可以是 SEEK_SET(开头)、SEEK_CUR(当前位置)或 SEEK_END(文件末尾)。

对于 文本流,offset 必须为零,或者由 ftell 返回的值(在这种情况下 origin 必须是 SEEK_SET)。fseek 在错误时返回非零。

ftell 返回流的当前文件位置,或在错误时返回 -1。

rewind(fp) 等同于 fseek(fp, OL, SEEK_SET); clearerr(fp);

21 文件定位示例

```

#include

int main(void) {

Open a text file for update (reading and writing), first truncating the file to zero length if it exists or creating the file if it does not exist.

```

```

FILE *fp = fopen("demo.dat", "w+");

for (char ch = 'A'; ch <= 'E'; ch++) {

putc(ch, fp);

putc(' ', fp);

}

fflush(fp);

rewind(fp);

printf("rewind -> %ld \n", ftell(fp))

```

```

}

```

```

Output

rewind -> 0

```

22 文件定位示例

```

#include

int main(void) {

FILE *fp = fopen("demo.dat", "w+");

for (char ch = 'A'; ch <= 'E'; ch++) {

putc(ch, fp);

putc(' ', fp);

}

fflush(fp);

rewind(fp);

fseek(fp, 4, SEEK_SET);

printf("fseek+4 -> %ld\n", ftell(fp))

}

```

23 文件定位示例

```

#include

int main(void) {

FILE *fp = fopen("demo.dat", "w+");

for (char ch = 'A'; ch <= 'E'; ch++) {

putc(ch, fp);

putc(' ', fp);

}

fflush(fp);

rewind(fp);

fseek(fp, 4, SEEK_SET);

fseek(fp, 3, SEEK_CUR);

printf("fseekcur -> %ld\n", ftell(fp))

}

```

24 文件定位示例

```

#include

int main(void) {

FILE *fp = fopen("demo.dat", "w+");

for (char ch = 'A'; ch <= 'E'; ch++) {

putc(ch, fp);

putc(' ', fp);

}

fflush(fp);

rewind(fp);

fseek(fp, 4, SEEK_SET);

fseek(fp, 3, SEEK_CUR);

fseek(fp, -2, SEEK_END);

printf("fseekend -> %ld\n", ftell(fp))

}

```

25 文件定位示例

```

#include

int main(void) {

FILE *fp = fopen("demo.dat", "w+");

for (char ch = 'A'; ch <= 'E'; ch++) {

putc(ch, fp);

putc(' ', fp);

}

fflush(fp);

rewind(fp);

fseek(fp, 4, SEEK_SET);

fseek(fp, 3, SEEK_CUR);

fseek(fp, -2, SEEK_END);

rewind(fp);

printf("rewind -> %ld\n", ftell(fp));

}

```

| Demo.dat |

| :--- |

| $\mathrm{A} \square \mathrm{B} \square \mathrm{C} \square \mathrm{D} \square \mathrm{E} \square$ |

| fp |

| 输出
rewind $->0$ |

26 I/O 的 系统调用

本幻灯片中描述的所有函数都属于 标准 C 库

它们是围绕 系统调用包装器

系统调用 是一种 计算机程序 从其执行所在的 操作系统 内核 请求服务的 程序化 方式。

系统调用 是进入 内核代码 的唯一入口点,并在 内核模式 下执行(而不是 用户模式)。

27 文件描述符

文件描述符 是一个非负整数。当我们打开一个现有文件或创建一个新文件时,内核 会向进程返回一个 文件描述符

0 是 标准输入 (stdin),1 是 标准输出 (stdout),2 是 标准错误 (stderr)。

使用 系统调用 时,我们应该使用符合 POSIX 标准的符号常量 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO,而不是 0, 1, 2,以提高代码的可读性。(stdin、stdout、stderr 不起作用。)

28 打开文件

可以使用以下方式打开文件:

```

#include

int open(const char path, int oflag, ... / mode_t mode */ );

```

path 是要打开或创建的文件名。

oflag 参数可以是:

Oflags 可以通过 按位或 组合,例如:O_WRONLY | O_CREAT | O_TRUNC(还有其他我们在此课程中不会使用的常量。请参阅 APUE,第 63 页)

29 关闭文件

```

#include

int close(int fd);

```

通过调用 close 函数来关闭打开的文件。关闭文件还会释放进程可能在该文件上持有的任何 记录锁

30 从文件读取/写入文件

```

#include

ssize_t read(int fd, void *buf, size_t nbytes);

返回:读取的字节数,如果文件结束则为 0,错误时为 -1

#include

ssize_t write(int fd, const void *buf, size_t nbytes);

返回:如果成功写入的字节数,错误时为 -1

```

31 读取/写入示例

```

#define BUFSIZE 4096

int main(void) {

int n;

char buf[BUFSIZE];

while ((n = read(STDIN_FILENO, buf, BUFSIZE)) > 0) {

if (write(STDOUT_FILENO, buf, n) != n) {

fprintf(stderr, "Write error.\n");

}

}

if (n < 0) {

fprintf(stderr, "Read error.\n");

}

return 0;

}

```

32 读取目录内容

```

#include

struct dirent readdir(DIR dirp);

```

readdir() 函数返回一个指向 dirent 结构体的指针,该结构体表示由 dirp 指向的目录流中的下一个目录项。

在达到目录流末尾或发生错误时返回 NULL。

dirent 结构体的定义如下:

```

struct dirent {

ino_t d_ino; / Inode number /

off_t d_off; / Not an offset; see below /

unsigned short d_reclen; / Length of this record /

unsigned char d_type; /* Type of file; not supported

by all filesystem types */

char d_name[256]; / Null-terminated filename /

};

```

33 文件内容的 状态查询

出于本课程的目的,我们将重点关注 stat() 和 lstat()

34 stat 结构体

```

struct stat {

dev_t st_dev; / ID of device containing file /

ino_t st_ino; / Inode number /

mode_t st_mode; / File type and mode /

nlink_t st_nlink; / Number of hard links /

uid_t st_uid; / User ID of owner /

gid_t st_gid; / Group ID of owner /

dev_t st_rdev; / Device ID (if special file) /

off_t st_size; / Total size, in bytes /

blksize_t st_blksize; / Block size for filesystem I/O /

blkcnt_t st_blocks; / Number of 512 B blocks allocated /

struct timespec st_atim; / Time of last access /

struct timespec st_mtim; / Time of last modification /

struct timespec st_ctim; / Time of last status change /

};

```

35 使用 stat() 和 lstat()

int stat(char path, struct stat buf);

36 stat() 示例

这是一个代码片段,用于打印名为 "example.txt" 的文件的 用户 ID

```

struct stat filestat;

stat("example.txt", &filestat);

printf("%d\n", filestat.st_uid);

```

37 lstat() 示例

这是一个代码片段,用于打印指向 "example.txt" 的 符号链接用户 ID

```

struct stat lfilestat;

lstat("symlink", &lfilestat);

printf("%d\n", filestat.st_uid);

```

38 lstat() 示例

这是一个代码片段,用于打印指向 "example.txt" 的 符号链接用户 ID

```

struct stat lfilestat;

lstat("symlink", &lfilestat);

printf\"%d\n", filestat.st_uid);

```

如果我们将其替换为 stat ("symlink", &lfilestat),则有关 example.txt 的 元数据 将在 lfilestat 结构体 中。

39 缓冲

缓冲区”中来组合这些操作。

40 缓冲 类型